package com.ztspeech;

import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.ztspeech.vad.VadJni;
import com.ztspeech.vad.VoiceDetector;
import com.ztspeech.ztcodec.ZtCodec2;

import org.dragonnova.meetingapp.utils.LogUtil;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * 紫东语音识别接口
 * Created by Administrator on 2015/6/18.
 * Author: Songxy
 * Email: thinkdata@163.com
 */
public class ZTRecognizer extends AZTClient {

    private final static String TAG = ZTRecognizer.class.getName();
    /**
     *
     */
    private Thread recoginzerThread;
    /**
     * 末尾检测
     */
    private boolean isEndQueue = false;
    /**
     * 队列的计数器
     */
    private int count_queue = 0;
    /**
     * 队列
     */
    private ConcurrentLinkedQueue<byte[]> queues = new ConcurrentLinkedQueue<byte[]>();

    private VoiceDetector vad = new VoiceDetector(1);
    /**
     * 编码
     */
    private ZtCodec2 ztCodec = new ZtCodec2();
    /**
     * 加密缓冲区
     */
    private ByteBuffer encodeBuffer;
    /**
     * 最小采样帧长，按照10ms 16KHZ计算，与AudioRecord参数一致
     */
    private int WAVE_FRAME_SIZE = 320 * 2;
    /**
     * 编码后的语音数组
     */
    private byte[] encodeFrameBuffer = new byte[256];

    private long encoder = 0L;
    /**
     * 结果
     */
    private String result;
    /**
     * 当前的子线程,当不是当前子线程时不执行同步代码块中的内容
     */
    private Thread currentThread;

    Handler mHandler;

    private String serviceType;
    public static final String SERVICETYPE_SPEECHRECO = "sr";
    public static final String SERVICETYPE_SPEECHTRANS = "s2t";
    public static final String SERVICETYPE_SPEECHNLP = "s2n";

    private String speechCodec;
    public static final String SPEECHCODEC_OPUS = "opu";
    public static final String SPEECHCODEC_PCM = "pcm";
    public static final String SPEECHCODEC_PCM8 = "pc8";

    private String resultRetMode;
    public static final String RESULTRET_ALL = "0";
    public static final String RESULTRET_CONTINUOUS = "1";
    public static final String RESULTRET_CONTINUOUS_TEMP = "2";

    public void setResultRetMode(String mode) {
        resultRetMode = mode;
    }

    public void setServiceType(String svcType) {
        this.serviceType = svcType;
    }

    public void setSpeechCodec(String codec) {
        if (codec.equals(SPEECHCODEC_OPUS)) {
            this.encodeBuffer = ByteBuffer.allocate(AppAudio.MAX_WAVE_SIZE + 256);
        } else if (codec.equals(SPEECHCODEC_PCM8)) {
            this.encodeBuffer = ByteBuffer.allocate(AppAudio.MAX_WAVE_SIZE);
        }
        this.speechCodec = codec;
    }

    public ZTRecognizer(Handler hanlder) {
        this.mHandler = hanlder;

        this.setServiceType(SERVICETYPE_SPEECHRECO);
        this.setSpeechCodec(SPEECHCODEC_OPUS);
        this.setResultRetMode(RESULTRET_ALL);

        vad.setParam(VadJni.VD_PARAM_PREFRAMENUM, 20);
        vad.setParam(VadJni.VD_PARAM_MINVOCFRAMENUM, 30);
        vad.setParam(VadJni.VD_PARAM_MINVOCRATIO, 0.6f);
        vad.setParam(VadJni.VD_PARAM_MINSILFRAMENUM, 70);
        vad.setParam(VadJni.VD_PARAM_MINSILRATIO, 0.9f);
        vad.setParam(VadJni.VD_PARAM_MINENERGY, 300 * 300);
        vad.setParam(VadJni.VD_PARAM_MAXENERGY, 5000 * 5000);
    }

    /**
     * 识别线程
     */
    private Runnable recognizer = new Runnable() {
        @Override
        public void run() {
            Thread thisThread = Thread.currentThread();
            synchronized (this) {
                if (thisThread == ZTRecognizer.this.currentThread) {
                    //init
                    encoder = ztCodec.createEncoder();
                    encodeBuffer.rewind();
                    count_queue = queues.size();
                    while (true) {
                        try {
                            while (!queues.isEmpty()) {
                                byte[] data = queues.poll();
                                if (thisThread == ZTRecognizer.this.currentThread) {
                                    innerDoPost(data, 0, data.length, isEndQueue(),thisThread);
                                }
                                count_queue--;
                            }
                            if (isEndQueue()) {
                                innerDoPost(null, 0, 0, true, thisThread);
                                break;
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    /*//整个语音提交
                    if (thisThread == ZTRecognizer.this.currentThread) {
                        try {
                            Log.e(TAG, "recognizing........");
                            result = (String) ZTRecognizer.this.doPost(
                                    encodeBuffer.array(), 0, encodeBuffer.position());
                            Message msg = AppAudio.mHandler.obtainMessage(AppAudio.RECOGNIZED);
                            msg.obj = result;
                            if (thisThread == ZTRecognizer.this.currentThread) {
                                AppAudio.mHandler.sendMessage(msg);
                            }
                            Log.e(TAG, "recognized........");
                        } catch (Exception e) {
                            e.printStackTrace();
                            Message msg = AppAudio.mHandler.obtainMessage(AppAudio.RECOGNIZE_ERROR);
                            AppAudio.mHandler.sendMessage(msg);
                        }
                    }*/
                }
            }
        }
    };

    /**
     * from hucn： 增加的三个变量，POST_FRAME_SIZE表示每次传递多少帧进行翻译，tempFrameCount 表示计数，tempBuffer存储需要上传的数据
     */
    private static int POST_FRAME_SIZE = 3;
    private int tempFrameCount = 0;
    private ByteBuffer tempBuffer = ByteBuffer.allocate(AppAudio.MAX_WAVE_SIZE + 256);

    private void innerDoPost(byte[] data, int offset, int length, boolean isEnd, Thread thisThread) throws Exception {
        try {
            if (data != null && length > 0) {
                int j = 0;
                while (j < length) {
                    // TODO 按frame为一个单元
                    byte[] temp = new byte[WAVE_FRAME_SIZE];
                    System.arraycopy(data, j, temp, 0, WAVE_FRAME_SIZE);
                    if (this.speechCodec.equals(SPEECHCODEC_OPUS)) {
                        short[] shortBuf = this.byteArray2ShortArray(temp, 0, temp.length);
                        int encodeLen = this.ztCodec.encode(this.encoder, shortBuf, 0, encodeFrameBuffer);
                        this.encodeBuffer.put((byte) encodeLen);
                        this.encodeBuffer.put(encodeFrameBuffer, 0, encodeLen);
                        this.tempBuffer.put((byte) encodeLen);
                        this.tempBuffer.put(encodeFrameBuffer, 0, encodeLen);
                    } else if (this.speechCodec.equals(SPEECHCODEC_PCM8)) {
                        this.encodeBuffer.put(temp);
                    }
                    j += WAVE_FRAME_SIZE;
                    tempFrameCount++;
                }
                /**
                 * from hucn： 发送一定帧数的语音数据进行翻译
                 */
                if (tempFrameCount % POST_FRAME_SIZE == 0 || isEnd) {
                    String tempResult = "";
                    if (thisThread == ZTRecognizer.this.currentThread) {
                        try {
                            tempResult = (String) ZTRecognizer.this.doPostTemp(
                                    tempBuffer.array(), 0, tempBuffer.position(), isEnd);
                            LogUtil.e(TAG, "recognizing........");
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            tempBuffer.rewind();
                        }
                    }
                    if (!TextUtils.isEmpty(tempResult)) {
                        if (thisThread == ZTRecognizer.this.currentThread) {
                            Message msg = mHandler.obtainMessage(AppAudio.RECOGNIZE_TEMP_RESULT);
                            msg.obj = tempResult;
                            mHandler.sendMessage(msg);
                            LogUtil.e(TAG, "fragment =" + tempResult);
                        }
                    }
                }
            } else {
                String finalResult = "";
                if (thisThread == ZTRecognizer.this.currentThread) {
                    finalResult = (String) ZTRecognizer.this.doPostTemp(tempBuffer.array(), 0, tempBuffer.position(), true);
                }
                if (thisThread == ZTRecognizer.this.currentThread) {
                    Message msg = mHandler.obtainMessage(AppAudio.RECOGNIZE_TEMP_RESULT);
                    msg.obj = finalResult;
                    mHandler.sendMessage(msg);
                    LogUtil.e(TAG, "finalfragment =" + finalResult);
                    Message msg1 = mHandler.obtainMessage(AppAudio.RECOGNIZED);
                    mHandler.sendMessage(msg1);
                }
            }
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
    }

    private boolean isEndQueue() {
        synchronized (this) {
            if (this.isEndQueue && this.queues.isEmpty()) return true;
            return false;
        }
    }

    private short[] byteArray2ShortArray(byte[] byteData, int offset, int count) {
        short[] shortData = new short[count / 2];

        for (int shortPos = 0; offset + 1 < byteData.length && count > 1; ++shortPos) {
            shortData[shortPos] = (short) ((short) byteData[offset] + (short) byteData[offset + 1] * 256);
            offset += 2;
            count -= 2;
        }

        return shortData;
    }

    protected void releaseEncoder() {
        if (this.encoder != 0L) {
            this.ztCodec.destroyEncoder(this.encoder);
            this.encoder = 0L;
        }
    }

    protected void finalize() throws Throwable {
        this.releaseEncoder();
    }

    protected String formatParam() {
        if (this.language == null)
            this.language = LANGUAGE_ENGLISH;
        if (this.speechCodec == null)
            this.speechCodec = SPEECHCODEC_OPUS;
        if (this.resultRetMode == null)
            this.resultRetMode = RESULTRET_ALL;

        return String.format("&z=1&t=%s&i=%s&sc=%s&csr=%s",
                serviceType, language, speechCodec, resultRetMode);
    }

    protected String doResponse(@NonNull InputStream input) throws Exception {
        return this.doCommonResponse(input);
    }

    /**
     * 开始识别
     */
    public synchronized void start() {
        this.isEndQueue = false;
        recoginzerThread = new Thread(recognizer);
        currentThread = recoginzerThread;
        recoginzerThread.start();
    }

    /**
     * 停止且识别
     */
    public synchronized void stopThenRecognize() {
        this.isEndQueue = true;
    }

    /**
     * 停止不识别
     */
    public synchronized void stopAndNoRecognize() {
        this.isEndQueue = true;
        currentThread = null;
    }

    /**
     * 存放语音队列
     *
     * @param voiceData
     * @param offset
     * @param length
     * @param isEnd
     */
    public void inputVoice(byte[] voiceData, int offset, int length, boolean isEnd) {
        synchronized (this) {
            if (offset >= 0 && length > 0) {
                byte[] temp = new byte[length];
                System.arraycopy(voiceData, offset, temp, 0, length);
                this.queues.add(temp);
            }
        }
    }

    private String sessionCookie = "";

    /**
     * 连续识别的网络请求方法
     */
    public Object doPostTemp(byte[] input, int offset, int inputLength, boolean isEnd) throws Exception {
        String recoResult = "";
        String serverAddr = "115.231.106.179:8080";
        String serverPath = "/speechtrans/servlet?";
        String asrParams = "ztsdebug=ma&cs=97531&terminaltype=galamic&mictype=galamic";
        String asrAddr = "http://" + serverAddr + serverPath + asrParams
                + "&t=" + "sr" + "&sc=" + "opu" + "&i=" + language
                + "&csr=" + "2";
        asrAddr += "&m=" + "i";
        URL asrUrl = new URL(isEnd ? (asrAddr + "&z=1") : asrAddr);
        HttpURLConnection connection = null;
        int respCode;
        try {
            connection = (HttpURLConnection) asrUrl.openConnection();
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(10000);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestMethod("POST");
            connection.setFixedLengthStreamingMode(inputLength);
            connection.setRequestProperty("Content-Type", "application/octet-stream");
            connection.setRequestProperty("Content-Length", String.valueOf(inputLength));
            connection.setRequestProperty("Connection", "Keep-Alive");
            connection.setRequestProperty("Charset", "UTF-8");
            connection.setUseCaches(false);
            if (sessionCookie.length() > 0) {
                connection.setRequestProperty("Cookie", sessionCookie);
            }
            connection.connect();
            if (input != null && inputLength > 0) {
                connection.getOutputStream().write(input, offset, inputLength);
                connection.getOutputStream().flush();
            }
            respCode = connection.getResponseCode();
            if (respCode == HttpURLConnection.HTTP_OK) {
                String key;
                String cookie = "";
                for (int i = 1; (key = connection.getHeaderFieldKey(i)) != null; i++) {
                    if (key.equalsIgnoreCase("set-cookie")) {
                        cookie = connection.getHeaderField(key);
                        cookie = cookie.substring(0, cookie.indexOf(";"));
                    }
                }
                if (cookie.length() > 10) {
                    sessionCookie = cookie;
                }
                InputStream is = connection.getInputStream();
                try {
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(is, "utf-8"));
                    try {
                        String lines = "";
                        String line;
                        boolean hasResult = false;
                        while ((line = br.readLine()) != null) {
                            if (!hasResult) {
                                hasResult = true;
                            } else {
                                lines += "\n";
                            }
                            lines += line;
                        }
                        recoResult = lines;
                        if (hasResult) {
                            return recoResult;
                        }
                    } finally {
                        br.close();
                    }
                } finally {
                    is.close();
                }
            } else {
                Message msg = mHandler.obtainMessage(AppAudio.RECOGNIZE_ERROR);
                mHandler.sendMessage(msg);
            }
        } catch (EOFException e) {
            e.printStackTrace();
        } finally {
            if (connection != null)
                connection.disconnect();
        }
        return recoResult;
    }
}
